package com.cunshe.config;

import com.cunshe.common.ResponseVO;
import com.cunshe.exception.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.util.InvalidMimeTypeException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 统一异常处理
 *
 * @author xiao
 */
@ControllerAdvice
public class ExceptionRestAdvice {

    private Logger log = LoggerFactory.getLogger(ExceptionRestAdvice.class);
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private HttpServletResponse response;

    /**
     * 业务异常
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = BizException.class)
    public ResponseVO bizException(BizException e) {
        return ResponseVO.error(10000, e.getMessage());
    }

    /**
     * 字段参数异常
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = FieldException.class)
    public ResponseVO fieldException(FieldException e) {
        return ResponseVO.error(10001, e.getMessage());
    }

    /**
     * 业务异常
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = IllegalArgumentException.class)
    public ResponseVO illegalArgumentException(IllegalArgumentException e) {
        if ("Source must not be null".equals(e.getMessage()) || "Target must not be null".equals(e.getMessage())) {
            return ResponseVO.success();
        }
        String message = Optional.ofNullable(e.getMessage()).orElseGet(() -> "Parameter error");
        return ResponseVO.error(10002, message);
    }


    /**
     * HttpMediaTypeNotAcceptableException
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
    public String httpMediaTypeNotAcceptableException(HttpMediaTypeNotAcceptableException e) {
        log.error("HttpMediaTypeNotAcceptableException={}, msg={}", request.getRequestURI(), e.getMessage());
        return process(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.getReasonPhrase(), "error/404");
    }

    /**
     * InvalidMimeTypeException
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(value = InvalidMimeTypeException.class)
    public String invalidMimeTypeException(InvalidMimeTypeException e) {
        log.error("invalidMimeTypeException={}, msg={}", request.getRequestURI(), e.getMessage());
        return process(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.getReasonPhrase(), "error/404");
    }

    private String process(int code, String message, String page) {
        String header = request.getHeader("X-Requested-With");
        String method = request.getMethod();
        if("XMLHttpRequest".equals(header) || HttpMethod.POST.name().equals(method)) {
            Map<String, String> map = new HashMap<>();
            map.put("code", String.valueOf(code));
            map.put("message", message);
            try {
                response.setContentType("application/json; charset=utf-8");
                response.getWriter().println(new Gson().toJson(map));
            } catch (Exception e1) {
                log.error(e1.getMessage(), e1);
            }
            return null;
        }
        return page;
    }

    /**
     * 参数格式异常
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = NumberFormatException.class)
    public ResponseVO numberFormatException(NumberFormatException e) {
        String message = Optional.ofNullable(e.getMessage()).orElseGet(() -> "Parameter format error");
        return ResponseVO.error(10003, message);
    }

    /**
     * 参数异常
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = ParamsException.class)
    public ResponseVO paramsException(ParamsException e) {
        String message = Optional.ofNullable(e.getMessage()).orElseGet(() -> "Parameter error");
        return ResponseVO.error(10004, e.getMessage());
    }

    /**
     * 404
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(value = NotFoundPageException.class)
    public String NotFoundPageException(NotFoundPageException e) {
        log.error("NotFound={}", request.getRequestURI());
        return process(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.getReasonPhrase(), "error/404");
    }

    /** 请求参数验证错误 400 */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = BindException.class)
    public ResponseVO bindException(BindException e) {
        String field = e.getBindingResult().getFieldError().getField();
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        return ResponseVO.error(400, "Request parameter error " + message);
    }

    /**
     * 请求参数错误 400
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ResponseVO missingServletRequestParameterException(MissingServletRequestParameterException e) {
        return ResponseVO.error(400, "Request parameter error");
    }

    /**
     * 请求参数错误 400
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = MissingPathVariableException.class)
    public ResponseVO missingPathVariableException(MissingPathVariableException e) {
        return ResponseVO.error(400, "Request parameter error");
    }

    /**
     * 请求参数错误 400
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    public ResponseVO methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        return ResponseVO.error(400, "Request parameter error");
    }

    /**
     * 请求方式错误 405
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public ResponseVO httpRequestMethodNotSupportedException(Exception e) {
        return ResponseVO.error(405, "请求方式错误");
    }

    /**
     * 参数错误
     */
    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseVO methodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        List<ObjectError> errors = bindingResult.getAllErrors();
        List<String> message = errors.stream().map(error -> error.getDefaultMessage()).collect(Collectors.toList());
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            String s = objectMapper.writeValueAsString(message);
            return ResponseVO.error(1, s);
        } catch (Exception e1) {
            return ResponseVO.error(1, e1.getMessage());
        }
    }

    /**
     * 语言不支持
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(LanguageNotSupportException.class)
    public String languageNotSupportException(LanguageNotSupportException e) {
        return "error/404";
    }

    /**
     * 全局异常 500
     */
    @ResponseBody
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler(Exception.class)
    public ResponseVO handleException(Exception e) {
        log.error(e.getMessage(), e);
        return ResponseVO.error(500, "服务器繁忙，请稍后再试");
    }

}
